/*
	http://tlan-pc328:8085/index.php?title=Ajax
	
	1. Pensar na estrutura de HEADERS
		1. HEADER pra request;
		2. HEADER do response;
	2. Tratar redirect do server, status == 201
*/


function Ajax(id){
	this.constructor.hash[(id || this.constructor.length)] = this;
	this.constructor.length++;
	this.id = id || this.constructor.length;
	this.request = this.constructor.xmlhttp();
}

Ajax.prototype = {
	toString:function(){return "Ajax ["+this.id+"]";},
	returnType:"text/plain",

	constructor:Ajax,
	processing:false,	//indica se a conexao esta ativa

	TIMEOUT_ID:-1,
	timeout:0,
	
	type:"REQUEST",	
	method:"POST",
	postBody:"",

	response:{},
	request:{},

	async:true,
	url:"",

	READY_STATE:0,
	
	event:function(){
		if(!this.hasOwnProperty(arguments[0])) return;
		var name = Array.prototype.shift.call(arguments);
		return this[name].apply(this,arguments);
	},
	
	abort:function(){
		if(this.event("onAbort",false)!=false){
			//http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_a_1.html
			this.request.abort();
			this.processing = false;
		}
	},
	
	RUN:function(http){
		
		if(this.READY_STATE == http.readyState) return;
		
		switch(http.readyState){
			case 0: this.event("onUnInitialized",http); break;
			case 1:	this.event("onLoading",http); break;
			case 2:	this.event("onLoaded",http); break;
			case 3:	this.event("onInteractive",http); break;
			case 4:
				/** Erro incompreensivel do Firefox **/
				try{s = http.status;}catch(e){}

				this.processing = false;
				this.header.resolveToResponse(this,http);

				switch(s){
					
					case 200: //SUCCESS;
						try{
							this.response = this.constructor.MimeTypeFilter.render(this.returnType,this); 
							this.event("onSuccess",this.response);
							this.event("onComplete",this.response);
						}catch(e){
							this.event("onError",e); 
						}
						break;
					
					case 408: //TIMEOUT http://www.checkupdown.com/status/E408.html
					case 504:	
						if(this.event("onTimeout",this.response,true)==false) return false;
						break;
					
					case 552: //ABORT
						if(this.event("onAbort",true)!=false) http.abort(); 
						break;
				}

				this.clearTimeout();

				break;
		}

		this.READY_STATE = http.readyState;
	
	},
	
	//*****************************************************************	TIMEOUT
	setTimeout:function(t, call){
		function F(){
			var a = arguments.callee; 
			a.run.apply(a.scope,a.args)
			a.scope.abortTimeout();
		}
		F.scope = this;
		F.run = call || this.onTimeout || function(){return true;};
		F.args = [false]; //indica se o timeout e' do server
		return this.TIMEOUT_ID = window.setTimeout(F, (t||this.timeout)*1000);
	},
	abortTimeout:function(){
		this.abort();
		this.clearTimeout();
	},
	clearTimeout:function(){
		window.clearTimeout(this.TIMEOUT_ID);
		this.TIMEOUT_ID = -1;
	},
	
	//*****************************************************************	PARAMETROS
	addParam:function(p,v){		return this.params.add(p,v)},
	removeParam:function(p,v){	return this.params.remove(p,v);},
	getParam:function(p){		return this.params.hash[p];},
	params:{
		hash:{},
		add:function(p,v){
			if(typeof(p) == "string" || typeof(p) == "number"){
				this.hash[p] = v;
			}else{
				this.hash = p;
			}
		},
		remove:function(p){
			if(!p){ 
				this.hash = {};
			}else{
				delete this.hash[p];
			}
		},
		toString:function(){
			return encodeURI(new String().toQueryString(this.hash));
		}
	},
	
	//*****************************************************************	HEADERS
	addHeader:function(p,v){	return this.header.add(p,v)},
	removeHeader:function(p,v){	return this.header.remove(p,v);},
	getHeader:function(p){		return this.header.hash[p];},
	header:{
		hash:{			"Content-Type":"text/plain",
						"encoding":"UTF-8"},
		add:function(p,v){
			if(typeof(p) == "string" || typeof(p) == "number"){
				console.log(p,v)
				this.hash[p] = v;
			}else{
				this.hash = p;
			}
		},
		remove:function(p){
			if(!p){
				this.hash = {};
			}else{
				delete this.hash[p];
			}
		},
		resolveToRequest:function(o,http){
			if(o.method.toUpperCase() == "POST"){
				this.add({
					"Content-Type":"application/x-www-form-urlencoded",
					"Content-length":(o.params.toString().length), 
					"Connection":"close"
				});
			}
			for(var i in this.hash){
				http.setRequestHeader(i, this.hash[i]);
			}
		},
		resolveToResponse:function(o,http){
			var r = /\s*(.*?)\s*\:\s*(.*)\s*\n/gim, s = o.request.getAllResponseHeaders(), z;
			while(z=r.exec(s)) this.add(z[1],z[2]);
			return this.hash;
		}
	},

	process:function(url,handler,timeout){

		if(this.processing) this.abort();
	
		var u = url||this.url||window.location.href, b = null, o = this, r = this.request;
		
		switch(this.method.toUpperCase()){
			case "GET":	if(this.params.toString().length) u += (u.indexOf("?")==-1 ? '?' : '&') + this.params.toString();
			case "HEAD":
			case "POST": b = this.postBody || this.params.toString();
		}
		
		this.processing = true;
		
		if(	(handler && typeof(handler)=="function" && handler.call(this,u)==false) || this.event("onProcess")==false	)
				return false;
	
		r.onreadystatechange = function(){
			o.RUN.call(o, r);
		};

		r.open(this.method, u, this.async);
		
		this.header.resolveToRequest(this,r);

		if(this.timeout||timeout){
			if(this.TIMEOUT_ID > -1)this.clearTimeout();
			this.setTimeout(timeout);				
		}
		
		r.send(b);
		
		return this;
	}
	
}










Ajax.hash = {};
Ajax.length = 0;

Ajax.MimeTypeFilter = {
	"text/plain":		function(c){		return c.responseText;	},
	"application/json":	function(c,scope){	return (function(){return eval("("+c.responseText+")")}).call(scope||this);	},
	"text/javascript":	function(c){		return eval(c.responseText);	},
	"text/xml":			function(c){		return c.responseXML;	},
	"text/html":		function(c){		var b = document.createElement("div");b.innerHTML = c.responseText;return b.firstChild;	},
	render:function(mimeType,scope){
		return this[mimeType].call(scope,scope.request);
	}
}

Ajax.PROVIDERS = ["MSXML2.serverXMLHTTP","MSXML2.XMLHTTP.5.0","MSXML2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];

Ajax.xmlhttp = function(){
	var r;
	if(window.XMLHttpRequest){
		r = new XMLHttpRequest();
	}else if(window.ActiveXObject){
		var i=0;
		while(!r && i<this.PROVIDERS.length){
			try{ r = new ActiveXObject(this.PROVIDERS[(i++)]); }catch(e){}
		}
	}
	return r;
}
























/* ************************************************************************ Request */
Ajax.Request = function(id){
	this.constructor(id);
}
Ajax.Request.prototype = new Ajax();
Ajax.Request.prototype.constructor = Ajax;


/* ************************************************************************ Bind */
Ajax.Bind = function(id){
	this.constructor(id);
}
Ajax.Bind.prototype = new Ajax();
Ajax.Bind.prototype.constructor = Ajax;
Ajax.Bind.prototype.type = "BIND";


/* ************************************************************************ Updater */
Ajax.Updater = function(id, time){
	this.constructor(id);
	this.time = time || this.time;
	if(this.onLoop){
		function f(){
			var o = arguments.callee;
			o.run.apply(o.scope,o.args);
		}
		f.scope = this;
		f.run = this.process;
		f.args = [];
		this.IntervalID = window.setInterval(f,this.time);
	}
}
Ajax.Updater.prototype = new Ajax();
Ajax.Updater.prototype.constructor = Ajax;
Ajax.Updater.prototype.onLoop = false;
Ajax.Updater.prototype.type = "UPDATER";
Ajax.Updater.prototype.time = 1000;
			

